package drawmodel;

import java.awt.Font;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import javax.imageio.ImageIO;
import javax.media.opengl.DebugGL2;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import javax.media.opengl.glu.gl2.GLUgl2;


import com.jogamp.opengl.util.awt.TextRenderer;
import com.jogamp.opengl.util.gl2.GLUT;


public class ViewRender implements GLEventListener{
	 // The current time for global animation
    private int time = 0;
    // The amount to update time
    private int deltaT = 1;
    // The maximum number of frames to skip
    private int maxFrameSkip = 10;
	 // The delay between time updates
    private long delay;
    // The time last time we checked
    private long lastTime;
	
    // create model object 
	private DrawModel model;
	
	private int list=-1;
	
	// set the numofface and numof vertex to display in the frame
	private String faceVertex;
	
	// create text to be visible on frmae
	private TextRenderer renderer;
	
	// enable and disable lightning 
	private boolean enableLight=false;
	//enable and disable grid option
	private boolean enGrid=false;

	// create the background texture
    private int texture1;
    private int texture2;
    
    // mouse event to use mouse selection option
    private MouseEvent mouse;
    
    // draw a texture option
	private DrawTexture text;
	// camera left right up down movement
	private double angleH,angleV;
	//camera zooming option
	private int zoomCam;
	
	// set the ambient light
	private float ambR,ambG,ambB;
	
	//light position
	private float ligPosX,ligPosY,ligPosZ;
	
   public ViewRender(String fileName,int fps ){
	 // Delay between time updates in ms
       delay = 1000/fps;
       // Look at the time now
       lastTime = System.currentTimeMillis();
       
       //create the model object
		model=new DrawModel(fileName);
		text=new DrawTexture();
				
		//default camera angles
		angleH=0;
		angleV=0;
		zoomCam=10;
		
		//default light colour
		
		ambR=0.0f;
		ambG=0.0f;
		ambB=1.0f;
		
		
		//default light position

		ligPosX=1.0f;
		ligPosY=1.0f;
		ligPosZ=1.0f;

   }
   
	@Override
	public void display(GLAutoDrawable drawable) {
		GL2 gl=drawable.getGL().getGL2();
		GLU glu=new GLU();
		GLUT glut = new GLUT();
		 
		   // Update the time for as much time as has passed
	        long curTime = System.currentTimeMillis();
	        long numFrames = ((curTime - lastTime) / delay);
	        // Clamp numFrames to a minimum of 1 and a maximum of maxFrameSkip frames
	        numFrames = Math.max(numFrames,1l);
	        numFrames = Math.min(numFrames,maxFrameSkip);
	        for (long i = 0; i < numFrames; ++i)
	        {
	            updateTime();
	        }

		
	        // Clear the colour and depth buffer
		gl.glClear(GL2.GL_COLOR_BUFFER_BIT|GL2.GL_DEPTH_BUFFER_BIT);
		
		
		 // Clear the Model view matrix
		gl.glLoadIdentity();
		
		// set camera position
			
		glu.gluLookAt(0,0,zoomCam,0, 0, 0, 0, 1, 0);
		  // left right movement
			gl.glRotated(angleH, 0, 1, 0);
	      // up down movement
			gl.glRotated(angleV, 1, 0, 0);
			
		
	// draw the background
	        drawBackground(gl);
        
	        // create vertex and faces variable
		    faceVertex =" "+ model.numOfFaces() + " faces and " + model.numOfVetrices() +" vertices";
		    renderer.beginRendering(drawable.getWidth(), drawable.getHeight());
		    renderer.setColor(1.0f, 0.2f, 0.2f, 0.8f);
		    
		    // draw it to the screen
		    renderer.draw(faceVertex, 20, 10);
		    renderer.endRendering();
		   
		    //enable the light
		    enableLight(gl);
		    
		    // create the light
		    createLight0(gl,ambR,ambG,ambB);
		    

			 // Set the light position here - must be after the camera is set
	        float light_position[] = { ligPosX, ligPosY, ligPosZ, 0.0f };
	        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, light_position, 0);
	        
		    
		        float mat_emission[] = {0.8f, 0.3f, 0.6f, 1.0f };
		        //float mat_specular[] = { 0.5f, 0.5f, 0.5f, 1.0f };
		   		        
		        // Disable all ambient lighting
		        //float [] ambient_color = {0,0,0};
		        //gl.glLightModelfv(GL2.GL_LIGHT_MODEL_AMBIENT, ambient_color, 0);
		        
		        // Set up the material
		        gl.glMaterialfv(GL.GL_FRONT, GL2.GL_EMISSION, mat_emission, 0);
		        //gl.glMaterialfv(GL.GL_FRONT, GL2.GL_SPECULAR, mat_specular, 0);
		      
		        
		        // enable the depth test 
		        gl.glEnable(GL2.GL_DEPTH_TEST);
		        
		        //draw the model
		        model.draw(gl);
		        
		        //disable the depth test
		        gl.glDisable(GL2.GL_DEPTH_TEST);
		      
		   // draw the grid
		   if(this.isEnGrid()){
			   drawHundred(gl);
			}
		   
		 		   
		  		   
		   // record the time so we can measure the time difference next frame
	   
	        if (delay == 0 || numFrames == maxFrameSkip)
	        {
	            lastTime = curTime;
	        }
	        else
	        {
	            lastTime += numFrames * delay;
	        }
	        
	        // flush the data to the screen or window 
		gl.glFlush();
			
	}
	
	
	

	
	 /**
     * (non-Javadoc)
     * @see javax.media.opengl.GLEventListener#init(javax.media.opengl.GLAutoDrawable)
     * 
     * This function initialises the OpenGL environment
     */

	@Override
	public void init(GLAutoDrawable drawable) {
		 drawable.setGL(new DebugGL2(drawable.getGL().getGL2()));
		 GL2 gl=drawable.getGL().getGL2();
		 
		  // Enable Smooth Shading
		 gl.glShadeModel(GL2.GL_SMOOTH);
		 
		 gl.glClearColor(1.0f,1.0f,1.0f,1.0f);
		 
		  // Depth Buffer Setup
	     gl.glClearDepth(1.0f);
	      
	     // Really Nice Perspective Calculations
	     gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL2.GL_NICEST);
	     
	     // the the object for draw string
	     renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 20));	
	     

	     
	}
	

    /**
     * (non-Javadoc)
     * @see javax.media.opengl.GLEventListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int)
     * 
     * Re-initialise the projection matrix and viewport after a reshape event
     */

	@Override
	public void reshape(GLAutoDrawable  drawable, int x, int y, int width,int height) {
		GL2 gl=drawable.getGL().getGL2();
    	GLU glu=new GLU();
    	  // Set up the viewport area
    	gl.glViewport(0, 0, width, height);
    	
    	if(height<0)
    		height=1;
    	 // Calculate the aspect ratio
    	float aratio=(float)width/(float)height;
    	 // Change to projection mode
    	gl.glMatrixMode(GL2.GL_PROJECTION);
    	  // Clear the projection matrix
    	gl.glLoadIdentity();
    	 // Set up projection as a perspective camera with 60 degree
        // field of view and viewing range up to 0.5 to 50m
    	glu.gluPerspective(60, aratio, 0.5, 50);
    	
        // calculate the model view matrix
    	gl.glMatrixMode(GL2.GL_MODELVIEW);
	     
	     try {
	      		texture1= readTexture("textures/sky.jpg",false,true,gl);
	      		//texture2= readTexture("textures/textured.jpg",false,true,gl);
	  		} catch (IOException e) {
	  			e.printStackTrace();	
	  		}
	     
	  		//set up background texture
	  	  gl.glBindTexture(GL2.GL_TEXTURE_2D, texture1);

	  	  gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_DECAL);
	      gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST_MIPMAP_NEAREST);
	      gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
	      gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
	      // Set up texture 2
	       /* gl.glBindTexture(GL2.GL_TEXTURE_2D, texture2);
	        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_REPEAT);
	        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST_MIPMAP_NEAREST);
	        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
	        gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
	        */
	        
	}
	
	
	
	/**
	 * create the grid modles
	 * @param gl The OpenGL context to use for rendering
	 */
	
	private void drawHundred(GL2 gl) {
		
		gl.glEnable(GL2.GL_DEPTH_TEST);
		for(int y=0;y<20;y+=2){
			 gl.glPushMatrix();
			 gl.glTranslatef(3,0,-y);
			 	for(int x=0;x<9;x++){
				 	gl.glTranslatef(3,0,0);
				    this.model.draw(gl);
				 
				 }
				 gl.glPopMatrix();
			}
		gl.glDisable(GL2.GL_DEPTH_TEST);
		
	}
	
	
	/**
	 * enable the lightning option
	 * @param gl The OpenGL context to use for rendering
	 */
	public void enableLight(GL2 gl){
		if(this.enableLight){
			gl.glEnable(GL2.GL_LIGHTING);
			return;
		}else{
			gl.glDisable(GL2.GL_LIGHTING);
			
		}
			
		
	}
	
	 /**
     * Set up GL_LIGHT0 as a strong white light with slightly blue specular hilights
     * and with quadratic attenuation
     * 
     * @param gl 
     *         The context to draw to 
     * @param ambient
     * 		   set the ambient light values
     */
    private void createLight0(GL2 gl, float ambR,float ambG,float ambB)
    {
        float light_ambient[] = { ambR,ambG, ambB, 1.0f };
        float light_diffuse[] = { 0.5f, 0.5f, 0.5f, 1.0f };
        
        // set up light colors
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, light_ambient, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, light_diffuse, 0);
        
        //enable the light0
        gl.glEnable(GL2.GL_LIGHT0);
        
    }
    
   	

	@Override
	public void dispose(GLAutoDrawable arg0) {
		
		
	}
	
	/**
	 * Point selection option
	 */
	public void pointSelect(GL2 gl){
		
		  int viewport[] = new int[4];
	      double mvmatrix[] = new double[16];
	      double promatrix[] = new double[16];
	      int realy = 0;// GL y coord pos
	     
	      
	      // world cordinates
	      double wcoord[] = new double[4];
	      if(this.mouse!=null){
	    	  int x = mouse.getX(); 
	    	  int y = mouse.getY();
	    	  
	    	  //get the view port matrix
	    	  gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
	    	  //get model view matrix
              gl.glGetDoublev(GL2.GL_MODELVIEW_MATRIX, mvmatrix, 0);
              //get projection matrix
              gl.glGetDoublev(GL2.GL_PROJECTION_MATRIX, promatrix, 0);
              // note view port[3] is height of window in pixels 
              
              realy = viewport[3] - (int) y - 1;
              
              //create the rgb color values
              float rgb[] = new float[3];
              
              //read the buffer
              gl.glReadBuffer(GL2.GL_FRONT);
              // read pixels from the world coordinates
              //gl.glReadPixels(x, realy, 1, 1, GL2.GL_RED, GL2.GL_FLOAT, rgb );
              
              // put every thing to move view model using mouse        
             // glu.gluUnProject((double) x, (double) realy, 1.0,  mvmatrix,promatrix, viewport, rgb[0],rgb[0],rgb[0]);
            
	    	  
	      }  
	      
	}
	
	/**
	 * draw a sky background
	 * 
	 * @param gl The OpenGL context to use for rendering
	 */
	 private void drawBackground(GL2 gl){
		 
		 // load the Projection matrix for ortho camera
	    	gl.glMatrixMode(GL2.GL_PROJECTION);
	    	gl.glPushMatrix();
	    	
	    	//clear the matrix
	    	gl.glLoadIdentity();
	    	
	    	//set uo ortho projection to background
	    	gl.glOrtho(0.0,9.0, 0.0, 9.0, -1.0, 1.0);
	    	gl.glMatrixMode(GL2.GL_MODELVIEW);
	    	gl.glPushMatrix();

	    	gl.glLoadIdentity();
	    	
	    	//enable the texture
	        gl.glEnable(GL2.GL_TEXTURE_2D);
	    	gl.glActiveTexture(GL2.GL_TEXTURE0);
	    	
	    	// bind the texture 
	        gl.glBindTexture(GL2.GL_TEXTURE_2D, texture1);
	         
	        //create background square
	         gl.glBegin(GL2.GL_QUADS);
	         
	         // set up texture coordinates
	          gl.glTexCoord2f(0,0);
	          gl.glVertex3f(0,0,0);
	          
	          gl.glTexCoord2f(0,1);
	          gl.glVertex3f(0,10,0);
	          
	          gl.glTexCoord2f(1,1);
	          gl.glVertex3f(10,10,0);
	          
	          gl.glTexCoord2f(1,0);
	          gl.glVertex3f(10,0,0);
	          
	        gl.glEnd();
	        
	        //disable the texture
	        gl.glDisable(GL2.GL_TEXTURE_2D);
	          
	        gl.glPopMatrix();

	        //back to projection matrix for glulook at
	        gl.glMatrixMode(GL2.GL_PROJECTION);
	        gl.glPopMatrix();
	        
	        // back to model view matrix 
	        gl.glMatrixMode(GL2.GL_MODELVIEW);
	  }
	 
	 
	/**
	 * set the amb light values
	 * @param ambLight
	 */
	public void setAmbLight(float ambR,float ambG,float ambB) {
		this.ambR+= ambR;
		if(this.ambR>1 ||this.ambR<0 ){
			this.ambR=0.5f;
		}
		this.ambG+= ambR;
		if(this.ambG>1){
			this.ambB=1;
		}
		this.ambB+= ambR;
		if(this.ambB>1){
			this.ambB=1;
		}
	}

	/**
	 *  set camera positions
	 * @param lightPos
	 */
	public void setLightPos(float x,float y, float z) {
		this.ligPosX+=x;
		this.ligPosY+=x;
		this.ligPosZ+=x;
		
		
	}

	/**
	 * set the camera positions
	 * 
	 * @param x 
	 * 			left right movement
	 * @param y 
	 * 			up down movement
	 * @param z
	 * 			camera zooming
	 */
	 public void camPositionChange(int x,int y,int z){
		 angleH=(angleH+x)%360;
		 angleV=(angleV+y)%360;
		 this.zoomCam+=z;
	    	
	}
	/**
	 * set the enable light variable
	 * @param enableLight
	 */
	public void setEnableLight(boolean enableLight) {
		this.enableLight = enableLight;
	}
	
	
	
	/**
	 * get the light variable
	 * @return 
	 * 		true if light is enable
	 */

    public boolean isEnableLight() {
		return enableLight;
	}
    
	/**
	 *  get the grid variable
	 * @return 
	 * 		grid is enable or disable
	 */
	public boolean isEnGrid() {
		return enGrid;
	}

	
	/**
	 * set the grid parameter
	 * @param enGrid
	 * 				to enable or disable a grid
	 */
	public void setEnGrid(boolean enGrid) {
		this.enGrid = enGrid;
	}
	

	/**
     * Reads a texture file from storage and loads it into an OpenGL object
     * 
     * @param filename The name of the file to read in as a texture
     * @param alpha Whether or not to read alpha values from the file
     * @param mipmap Whether or not to generate mipmaps for the texture
     * @param gl The GL2 object to use for calls to opengl
     * @return The OpenGL texture object identifier
     * @throws IOException Throws an exception if file can not be read
     */
    public int readTexture(String filename, boolean alpha, boolean mipmap, GL2 gl) throws IOException {
        // Read the file in to a BufferedImage using the ImageIO library
        BufferedImage img = ImageIO.read(new FileInputStream(filename));
        // Create an array to hold the raw pixel data
        int[] packedPixels = new int[img.getWidth() * img.getHeight()];

        // Read the pixel data out of the image and into the array
        PixelGrabber pixelgrabber = new PixelGrabber(img, 0, 0, img.getWidth(), img.getHeight(), packedPixels, 0, img.getWidth());
        try {
            pixelgrabber.grabPixels();
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }

        // Create a new buffer to store the data in a format that is useful for OpenGL
        int bytesPerPixel = alpha ? 4 : 3;
        ByteBuffer unpackedPixels = ByteBuffer.allocate(packedPixels.length * bytesPerPixel);

        // Go through the image in REVERSE row order copying the pixel data into the buffer
        for (int row = img.getHeight() - 1; row >= 0; row--) {
            for (int col = 0; col < img.getWidth(); col++) {
                // Use bit-shifting to extract the red green and blue components from the
                // packed pixel values
                int packedPixel = packedPixels[row * img.getWidth() + col];
                unpackedPixels.put((byte) ((packedPixel >> 16) & 0xFF));
                unpackedPixels.put((byte) ((packedPixel >> 8) & 0xFF));
                unpackedPixels.put((byte) ((packedPixel >> 0) & 0xFF));
                // If required, also extract the alpha component
                if (alpha) {
                    unpackedPixels.put((byte) ((packedPixel >> 24) & 0xFF));
                }
            }
        }

        // Finished using the buffer for reading, so flip it
        unpackedPixels.flip();

        // Create a new OpenGL texture object
        final int[] tmp = new int[1];
        gl.glGenTextures(1, tmp, 0);
        GLUgl2 glu = new GLUgl2();
        
        // Bind to the new texture
        gl.glBindTexture(GL2.GL_TEXTURE_2D, tmp[0]);
        
        // Set the formats appropriately depending on whether or not we have an alpha channel to load
        int internalFormat = GL2.GL_RGB8;
        int format = GL2.GL_RGB;
        if (alpha) {
            internalFormat = GL2.GL_RGBA8;
            format = GL2.GL_RGBA;
        }
        
        // Either create just the base level texture or else create the full set of mipmaps.
        if (mipmap) {
            glu.gluBuild2DMipmaps(GL2.GL_TEXTURE_2D, internalFormat, img.getWidth(), img.getHeight(), format, GL2.GL_UNSIGNED_BYTE, unpackedPixels);
        }
        else {
            gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, internalFormat, img.getWidth(), img.getHeight(), 0, format, GL.GL_UNSIGNED_BYTE, unpackedPixels);
        }
        
        // Unbind the texture object
        gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
        
        // Return the texture object
        return tmp[0];
    }
    
    /**
     * Update the time variable used in the world. Note this is called from multiple
     * threads and so must be synchronized
     */
    private void updateTime() {
        // Increase time by the update amount. When deltaT is set
        // to zero this function has no effect.
        time += deltaT;
    }
	
}
